package com.funcourse.demo.aliyun.contentsafe;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.funcourse.demo.aliyun.contentsafe.io.CheckScene;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckRequestBody;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckRequestBody.Task;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckRequestBody.TaskImage;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckRequestBody.TaskText;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckResponseBody;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckResponseBody.DataContent;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckResponseBody.Result;
import com.funcourse.demo.aliyun.contentsafe.io.ContentCheckResponseBody.Suggestion;
import com.funcourse.demo.model.Quiz;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

@Service
@Slf4j
public class ContentChecker {

  /**
   * https://help.aliyun.com/document_detail/107743.html?spm=a2c4g.11186623.6.609.3ced6eaf5wgD1e
   * http(s)://green.{region}.aliyuncs.com/业务API
   */
  private static final String CONTENT_CHECK_SERVICE_TEMPLAE="http://green.%s.aliyuncs.com%s";
  private static final String TEXT_SCAN_URL="/green/text/scan";
  private static final String IMAGE_SCAN_URL="/green/image/scan";

  @Autowired
  RestTemplate restTemplate;
  @Autowired
  ObjectMapper objectMapper;
  @Autowired
  ContentSafeConfig contentSafeConfig;

  public Boolean checkIfImagePass(String url){
    return checkContent(buildImageContentCheckBody(url),IMAGE_SCAN_URL);
  }
  public Boolean checkIfTextPass(String content){
    return checkContent(buildTextContentCheckBody(content),TEXT_SCAN_URL);
  }
  public Boolean checkIfTextPass(Quiz quiz){
    StringBuilder stringBuilder = extractStringFromQuiz(quiz);
    return checkContent(buildTextContentCheckBody(stringBuilder.toString()),TEXT_SCAN_URL);
  }

  private StringBuilder extractStringFromQuiz(Quiz quiz) {
    StringBuilder stringBuilder = new StringBuilder(quiz.getName());
    stringBuilder.append(quiz.getDescription());
    if(CollectionUtils.isEmpty(quiz.getQuestions())){
      return stringBuilder;
    }
    quiz.getQuestions().forEach(question -> {
      stringBuilder.append(question.getQuestionStem());
      stringBuilder.append(" ");
      stringBuilder.append(question.getAnswerExplanation());
      stringBuilder.append(" ");
      if(CollectionUtils.isEmpty(question.getChoices())){
        return;
      }
      question.getChoices().forEach(choice -> {
        stringBuilder.append(choice.getChoiceDesc());
        stringBuilder.append(" ");
      });
    });
    return stringBuilder;
  }

  private Boolean checkContent(ContentCheckRequestBody contentCheckRequestBody,String serviceAddress) {
    HttpHeaders httpHeaders;
    try {
      httpHeaders = buildHeaders(contentCheckRequestBody, serviceAddress);
    } catch (JsonProcessingException e) {
      log.error("processing header build failed",e);
      return true;
    }
    HttpEntity requestEntity=new HttpEntity(contentCheckRequestBody,httpHeaders);
    ResponseEntity<ContentCheckResponseBody> contentCheckResponseBodyResponseEntity;
    try{
      contentCheckResponseBodyResponseEntity = restTemplate
          .exchange(String.format(CONTENT_CHECK_SERVICE_TEMPLAE, contentSafeConfig.getRegion(), serviceAddress),
              HttpMethod.POST,
              requestEntity,
              ContentCheckResponseBody.class);
    }catch (HttpStatusCodeException exception) {
      log.error("ali content check service not avaliable {} {}",exception.getStatusCode().value(),exception.getResponseBodyAsString(),exception);
      return true;
    }

    if(!contentCheckResponseBodyResponseEntity.getStatusCode().is2xxSuccessful()){
      log.error("ali content check service not avaliable {}",contentCheckResponseBodyResponseEntity.getStatusCode().value());
      final ContentCheckResponseBody body = contentCheckResponseBodyResponseEntity.getBody();

      return true;
    }
    ContentCheckResponseBody body = contentCheckResponseBodyResponseEntity.getBody();
    try {
      log.debug("content check result is {}",objectMapper.writeValueAsString(body));
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }
    final List<DataContent> bodyData = body.getData();
    if(CollectionUtils.isEmpty(bodyData)){
      return true;
    }
    for (DataContent dataContent : bodyData) {
      List<Result> results = dataContent.getResults();
      if(CollectionUtils.isEmpty(results)){
        continue;
      }
      for (Result result : results) {
        if (null != result.getSuggestion() && Suggestion.block == result.getSuggestion()) {
          return false;
        }
      }
    }
    return true;
  }

  private HttpHeaders buildHeaders(ContentCheckRequestBody contentCheckRequestBody,String serviceAddress)
      throws JsonProcessingException {
    StringBuilder signatureContent = new StringBuilder("");
    signatureContent.append("POST\n");
    HttpHeaders headers = new HttpHeaders();
    String acceptHeader = "application/json";
    headers.add("Accept", acceptHeader);
    signatureContent.append(acceptHeader);
    signatureContent.append("\n");

    //根据请求body计算所得，算法如下：对body做md5（返回二进制格式），然后做base64编码。
    String contentMD5 = Base64.getEncoder().encodeToString(
        DigestUtils.md5Digest(objectMapper.writeValueAsString(contentCheckRequestBody).getBytes()));
    headers.add("Content-MD5", contentMD5);
    signatureContent.append(contentMD5);
    signatureContent.append("\n");

    String contentType = "application/json";
    headers.add("Content-Type", contentType);
    signatureContent.append(contentType);
    signatureContent.append("\n");

    //GMT日期格式，例如：Tue, 17 Jan 2017 10:16:36 GMT。 星期三, 十一月 18, 2020 02:21:38 上午 GMT
    String dateTime = formateTime(new Date());
    headers.add("Date", dateTime);
    signatureContent.append(dateTime);
    signatureContent.append("\n");

    final String colon = ":";

    final String signatureMethodName = "x-acs-signature-method";
    String signatureMethod = "HMAC-SHA1";
    headers.add(signatureMethodName, signatureMethod);
    signatureContent.append(signatureMethodName);
    signatureContent.append(colon);
    signatureContent.append(signatureMethod);
    signatureContent.append("\n");

    //随机字符串，用来避免回放攻击。
    final String signatureNonceName = "x-acs-signature-nonce";
    String nOnce = UUID.randomUUID().toString();
    headers.add(signatureNonceName,nOnce);
    signatureContent.append(signatureNonceName);
    signatureContent.append(colon);
    signatureContent.append(nOnce);
    signatureContent.append("\n");

    final String sigVersionName = "x-acs-signature-version";
    String sigVersion = "1.0";
    headers.add(sigVersionName, sigVersion);
    signatureContent.append(sigVersionName);
    signatureContent.append(colon);
    signatureContent.append(sigVersion);
    signatureContent.append("\n");

    final String acsVersionName = "x-acs-version";
    String acsVersion = "2018-05-09";
    headers.add(acsVersionName, acsVersion);
    signatureContent.append(acsVersionName);
    signatureContent.append(colon);
    signatureContent.append(acsVersion);
    signatureContent.append("\n");

    signatureContent.append(serviceAddress);
    log.debug("to sign is {}",signatureContent.toString());

    byte[] hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_1, contentSafeConfig.getAccessKeySecret()).hmac(signatureContent.toString().getBytes());
    final String encodedSignature = Base64.getEncoder().encodeToString(hmac);
    final String authorizationValue =
        "acs " + contentSafeConfig.getAccessKeyId() + colon + encodedSignature;
    headers.add("Authorization",
        authorizationValue);
    return headers;
  }

  private String formateTime(Date date){
    final SimpleDateFormat sdf =
        new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.ENGLISH);

// Give it to me in GMT time. Tue, 17 Jan 2017 10:16:36 GMT Wed, 18 Nov 2020 02:26:29 GMT
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
   return sdf.format(new Date());
  }

  private ContentCheckRequestBody buildTextContentCheckBody(String content) {
    ContentCheckRequestBody contentCheckRequestBody = new ContentCheckRequestBody();
    List<CheckScene> scens = new LinkedList<>();
    scens.add(CheckScene.antispam);
    contentCheckRequestBody.setScenes(scens);
    List<Task> tasks = new LinkedList<>();
    TaskText task = new TaskText();
    task.setContent(content);
    tasks.add(task);
    contentCheckRequestBody.setTasks(tasks);
    return contentCheckRequestBody;
  }
  private ContentCheckRequestBody buildImageContentCheckBody(String url) {
    ContentCheckRequestBody contentCheckRequestBody = new ContentCheckRequestBody();
    List<CheckScene> scens = new LinkedList<>();
    scens.add(CheckScene.porn);
    scens.add(CheckScene.terrorism);
    scens.add(CheckScene.ad);
    contentCheckRequestBody.setScenes(scens);
    List<Task> tasks = new LinkedList<>();
    TaskImage task = new TaskImage();
    task.setUrl(url);
    tasks.add(task);
    contentCheckRequestBody.setTasks(tasks);
    return contentCheckRequestBody;
  }
}
